import random

import numpy
import numpy as np

from scheduling_environment.jobShop import JobShop
from scheduling_environment.operation import Operation
from solution_methods.GA.src.heuristics import global_load_balancing_scheduler, local_load_balancing_scheduler, \
    random_scheduler
from test import toolbox


def select_next_operation_from_job(jobShopEnv: JobShop, job_id) -> Operation:
    """
    从给定的作业车间环境中选择下一个可调度的操作。

    参数:
        jobShopEnv (JobShop): 作业车间环境。
        job_id: 作业ID。

    返回:
        Operation: 下一个可调度的操作。
    """
    # 遍历所有可调度的操作，找到与job_id匹配的操作并返回
    for operation in jobShopEnv.operations_available_for_scheduling:
        if operation.job_id == job_id:
            return operation


def pox_crossover(ind1, ind2, nr_preserving_jobs):
    """
    使用部分映射交叉（POX）对两个个体进行交叉操作。

    参数:
        ind1: 第一个个体。
        ind2: 第二个个体。
        nr_preserving_jobs: 保留的作业数量。

    返回:
        tuple: 两个新个体。
    """
    # 随机选择nr_preserving_jobs个作业作为保留作业
    preserving_jobs = random.sample(range(1, max(ind1)), nr_preserving_jobs)

    # 构建新的序列，保留部分来自ind1，其余来自ind2
    new_sequence_ind1 = list(filter(lambda a: a not in preserving_jobs, ind2))
    for i in range(len(ind1)):
        if ind1[i] in preserving_jobs:
            new_sequence_ind1.insert(i, ind1[i])

    # 构建新的序列，保留部分来自ind2，其余来自ind1
    new_sequence_ind2 = list(filter(lambda a: a not in preserving_jobs, ind1))
    for i in range(len(ind2)):
        if ind2[i] in preserving_jobs:
            new_sequence_ind2.insert(i, ind2[i])

    return new_sequence_ind1, new_sequence_ind2


def mutate_shortest_proc_time(individual, indpb, jobShopEnv: JobShop):
    """
    对个体进行变异操作，选择最短处理时间的机器。

    参数:
        individual: 个体。
        indpb: 每个基因变异的概率。
        jobShopEnv (JobShop): 作业车间环境。

    返回:
        individual: 变异后的个体。
    """
    # 对每个基因以indpb概率进行变异，选择最短处理时间的机器
    for i, _ in enumerate(individual):
        if random.random() < indpb:
            operation = jobShopEnv.operations[i]
            individual[i] = np.argmin(operation.processing_times)
    return individual


def mutate_sequence_exchange(individual, indpb):
    """
    对个体进行变异操作，交换两个随机位置的基因。

    参数:
        individual: 个体。
        indpb: 每个基因变异的概率。

    返回:
        individual: 变异后的个体。
    """
    # 对每个基因以indpb概率进行变异，随机交换两个位置的基因
    for i in range(len(individual)):
        if random.random() < indpb:
            j = random.choice([index for index in range(len(individual)) if index != i])
            individual[i], individual[j] = individual[j], individual[i]
    return individual


# 初始化HHO哈里斯鹰算法中的个体（使用随机动作选择启发式）
def init_individual(ind_class, jobShopEnv):
    """
    创建个体，个体是一个包含机器选择（选项索引）和操作序列（作业索引）的列表。

    这里和遗传算法通用

    参数:
        ind_class: 个体类。
        jobShopEnv (JobShop): 作业车间环境。

    返回:
        ind_class: 初始化后的个体。
    """
    rand = random.random()
    if rand <= 0.6:  # 60% 初始分配使用全局选择调度器
        jobShopEnv = global_load_balancing_scheduler(jobShopEnv)
    elif rand <= 0.9:  # 30% 初始分配使用局部选择调度器
        jobShopEnv = local_load_balancing_scheduler(jobShopEnv)
    else:  # 10% 初始分配使用随机调度器
        jobShopEnv = random_scheduler(jobShopEnv)

    # 获取操作序列和机器分配列表
    operation_sequence = [operation.job_id for operation in jobShopEnv.scheduled_operations]
    machine_selection = [
        (operation.operation_id, sorted(list(operation.processing_times.keys())).index(operation.scheduled_machine))
        for operation in jobShopEnv.scheduled_operations]
    machine_selection.sort()
    machine_selection = [allocation for _, allocation in machine_selection]
    jobShopEnv.reset()
    return ind_class([machine_selection, operation_sequence])


# 初始化种群
def init_population(toolbox, population_size):
    """
    初始化种群。

    这里和遗传算法通用

    参数:
        toolbox: DEAP工具箱。
        population_size: 种群大小。

    返回:
        list: 初始化后的种群。
    """
    return [toolbox.init_individual() for _ in range(population_size)]


def evaluate_individual(individual, jobShopEnv: JobShop, reset=True):
    """
    评估个体的适应度。

    这里和遗传算法通用

    参数:
        individual: 个体。
        jobShopEnv (JobShop): 作业车间环境。
        reset: 是否重置环境。

    返回:
        tuple: 适应度值和作业车间环境。
    """
    jobShopEnv.reset()
    jobShopEnv.update_operations_available_for_scheduling()
    for i in range(len(individual[0])):
        job_id = individual[1][i]
        operation = select_next_operation_from_job(jobShopEnv, job_id)
        operation_option_index = individual[0][operation.operation_id]
        # machine_id = sorted(operation.processing_times.keys())[operation_option_index]

        # debug用
        sorted_operation_processing_times = sorted(operation.processing_times.items())

        # debug用
        length = len(sorted_operation_processing_times)

        # print(sorted_operation_processing_times)
        # info = operation.processing_times.keys()
        if (operation_option_index >= length):
            print(f"len为 {length} ,operation_option_index为 {operation_option_index}")
            operation_option_index = operation_option_index - 1
            print("修复后")
            print(f"len为 {length} ,operation_option_index为 {operation_option_index}")

        try:
            machine_id = sorted(operation.processing_times.keys())[operation_option_index]
        except IndexError:
            # 处理索引越界的情况
            print(
                f"警告: operation_option_index {operation_option_index} 超出了 keys {sorted(operation.processing_times.keys())} 的范围")
            # 可以选择一个默认值或者采取其他措施
            machine_id = 0  # 或者其他适当的默认值,选择机器id为0,第一个元素

        duration = operation.processing_times[machine_id]

        jobShopEnv.schedule_operation_with_backfilling(operation, machine_id, duration)
        jobShopEnv.update_operations_available_for_scheduling()

    makespan = jobShopEnv.makespan

    if reset:
        jobShopEnv.reset()
    return makespan, jobShopEnv


def evaluate_population(toolbox, population):
    """
    评估种群中所有个体的适应度。

    这里和遗传算法通用

    参数:
        toolbox: DEAP工具箱。
        population: 种群。

    返回:
        list: 所有个体的适应度值。
    """
    # 并行评估种群
    population = [[ind[0], ind[1]] for ind in population]
    a = population
    # b = population[0]
    # c = a.ind[1]
    fitnesses = toolbox.map(toolbox.evaluate_individual, population)
    fitnesses = [(fit[0],) for fit in fitnesses]

    return fitnesses


def explore1(ind, ind_rand):
    # 传入个体，然后探索,根据种群中其他个体位置 飞行
    # ind[0] = [random.randint(0, len(ind[0]) - 1) for _ in range(len(ind[0]))] # 对机器选择MS序列进行探索
    # offspring = X_rand - random.random() * abs(X_rand - 2 * random.random() * population[i])

    # 对机器选择MS序列进行探索
    # ind[0] = ind_rand[0] - random.random() * abs(ind_rand[0] - 2 * random.random() * ind[0])

    # HHO原公式
    # X[i, :] = X_rand - random.random() * abs(
    #     X_rand - 2 * random.random() * X[i, :]
    # )

    # 将 ind 和 ind_rand 转换为 NumPy 数组
    b = ind
    ind = np.array(ind)
    ind_rand = np.array(ind_rand)
    new_ind = ind

    new_ind[0] = ind_rand[0] - random.random() * abs(ind_rand[0] - 2 * random.random() * ind[0])

    # ind[1], ind_rand[1] = toolbox.mate_POX(ind[1], ind_rand[1])

    # ind1[1], ind2[1] = toolbox.mate_POX(ind1[1], ind2[1])

    # del ind.fitness.values, ind_rand.fitness.values

    a = new_ind
    return new_ind.tolist()
    # OS部分直接复制
    # return ind


def explore2(ind, Rabbit_Location, AVG_Location, ub, lb):
    # 传入个体，然后探索，随机栖息到一颗高树
    # ind[0] = ind_rand[0] - random.random() * abs(ind_rand[])
    # offspring = Rabbit_Location - np.mean(population, axis=0) - random.random() * (
    #         (ub - lb) * random.random() + lb)
    # 对机器选择MS序列进行探索

    # 原公式
    # perch on a random tall tree (random site inside group's home range)
    # X[i, :] = (Rabbit_Location - X.mean(0)) - random.random() * (
    #         (ub - lb) * random.random() + lb
    # )

    b = ind
    ind = np.array(ind)
    new_ind = ind
    # ind[0] = ind[0] - AVG_Location - random.random() * ((ub - lb)*random.random() + lb)
    new_ind[0] = Rabbit_Location[0] - AVG_Location - random.random() * ((ub - lb) * random.random() + lb)
    e = new_ind[0].tolist()
    new_ind[0] = np.clip(new_ind[0], lb, ub)
    c = new_ind
    return new_ind.tolist()


# MS机器选择部分，编码
def encode(operations_machine_num, population):
    for i in range(len(population)):
        ind_temp = []
        for j in range(len(operations_machine_num)):
            ind_temp.append(population[i][0][j] / operations_machine_num[j])
        population[i][0] = ind_temp
    return population


# MS机器选择部分，解码
def decode(operations_machine_num, population):
    for i in range(len(population)):
        ind_temp = []
        for j in range(len(operations_machine_num)):
            ind_temp.append(round(population[i][0][j] * operations_machine_num[j]))
        population[i][0] = ind_temp
    return population


# 这是 哈里斯鹰算法 捕捉兔子 的四种不同攻击方式，但是为了统一，也命名为variation变化
def variation(population, toolbox, E1, pop_size, ub, lb, dim, Rabbit_Location):
    """

    参数:


    返回:
        list: 变异后的种群。
    """
    offspring = []
    # Rabbit_Location = numpy.zeros(dim)  # 最优解的位置

    # population = encode(dim, population)

    for i in range(int(pop_size)):

        # # 调用目标函数，返回适应度值
        # fitness= evaluate_individual(random.choice(population), jobShopEnv=env)
        #
        # # 更新兔子的能量
        # # 当适应度值小于兔子能量时，更新兔子能量为该适应度值，同时更新兔子位置为该位置

        # 通过逃逸能量系数 计算出逃逸能量
        E0 = 2 * random.random() - 1  # -1<E0<1
        Escaping_Energy = E1 * (
            E0
        )  # escaping energy of rabbit Eq. (3) in the paper

        ind = population[i]  # 获取当前个体

        # -------- Exploration phase Eq. (1) in paper -------------------
        if abs(Escaping_Energy) >= 1:  # 探索阶段
            q = random.random()
            rand_Hawk_index = random.randint(0, pop_size - 1)
            ind_rand = population[rand_Hawk_index]  # 随机选择一只hawk个体
            # X_rand = population[rand_Hawk_index].copy()
            if q < 0.5:
                # offspring = X_rand - random.random() * abs(X_rand - 2 * random.random() * population[i])
                new_ind = explore1(ind, ind_rand)
            else:

                a = population[0]
                b = numpy.array(population[0])
                c = numpy.array(population[0][0])

                # 把种群的MS段收集起来,debug用
                MS = []
                for i in range(len(population)):
                    MS.append(population[i][0])
                d = MS  # 处理前的种群MS numpy数组
                MS = numpy.array(MS)
                AVG_Location = numpy.mean(MS, axis=0)

                e = MS
                new_ind = explore2(ind, Rabbit_Location, AVG_Location, ub, lb)

                # # 遍历population数组的第一个list，求出第一个list的平均位置
                # AVG_Location = numpy.mean(population[0], axis=0)
                # AVG_Location = numpy.mean(population, axis=0)

                # 原公式
                # perch on a random tall tree (random site inside group's home range)
                # X[i, :] = (Rabbit_Location - X.mean(0)) - random.random() * (
                #         (ub - lb) * random.random() + lb
                # )
                # offspring = Rabbit_Location - np.mean(population, axis=0) - random.random() * (
                #         (ub - lb) * random.random() + lb)
                # new_ind = explore2(ind,AVG_Location, ub, lb)

        # offspring = decode(operations_machine_num, offspring)
        # 删除旧的适应度值
        del ind.fitness.values
        offspring.append(ind)

        # # -------- Exploitation phase -------------------
        # else:  # 开发阶段
        #     r = random.random()
        #
        #     # phase 1: ----- surprise pounce (seven kills) ----------
        #     # surprise pounce (seven kills): multiple, short rapid dives by different hawks
        #     Jump_strength = 2 * (1 - random.random())
        #
        #     if r >= 0.5 and abs(Escaping_Energy) < 0.5:  # Hard besiege 硬包围
        #         offspring = Rabbit_Location - Escaping_Energy * abs(Rabbit_Location - population[i])
        #
        #     elif r >= 0.5 and abs(Escaping_Energy) >= 0.5:  # Soft besiege 软包围
        #         offspring = (Rabbit_Location - population[i]) - Escaping_Energy * abs(
        #             Jump_strength * Rabbit_Location - population[i])
        #
        #
        #     # phase 2: --------performing team rapid dives (leapfrog movements)----------
        #     elif r < 0.5 and abs(Escaping_Energy) >= 0.5:  # Soft besiege with levy flight
        #         X1 = Rabbit_Location - Escaping_Energy * abs(Jump_strength * Rabbit_Location - population[i])
        #         X1 = np.clip(X1, lb, ub)
        #         if evaluate_population(toolbox, X1) < evaluate_population(toolbox, population):
        #             offspring = X1.copy()
        #
        #         else:  # Perform levy-based short rapid dives around the rabbit  快速俯冲的
        #             X2 = Rabbit_Location - Escaping_Energy * abs(Jump_strength * Rabbit_Location - population[i]) + \
        #                  np.multiply(np.random.randn(len(population[i])), Levy(len(population[i])))
        #             X2 = np.clip(X2, lb, ub)
        #
        #             if evaluate_population(toolbox, X2) < evaluate_population(toolbox, population):
        #                 offspring = X2.copy()
        #
        #
        #     else:  # Hard besiege with levy flight
        #         X1 = Rabbit_Location - Escaping_Energy * abs(
        #             Jump_strength * Rabbit_Location - np.mean(population, axis=0))
        #         X1 = np.clip(X1, lb, ub)
        #
        #         if evaluate_population(toolbox, X1) < evaluate_population(toolbox, population):
        #             offspring = X1.copy()
        #
        #         else:  # Perform levy-based short rapid dives around the rabbit
        #             X2 = Rabbit_Location - Escaping_Energy * abs(
        #                 Jump_strength * Rabbit_Location - np.mean(population, axis=0)) + \
        #                  np.multiply(np.random.randn(len(population[i])), Levy(len(population[i])))
        #             X2 = np.clip(X2, lb, ub)
        #
        #             if evaluate_population(toolbox, X2) < evaluate_population(toolbox, population):
        #                 offspring = X2.copy()

        # # 确保新位置在边界内
        # offspring = np.clip(offspring, lb, ub)

    return offspring


# 注意：Levy函数需要定义来生成levy飞行分布
def Levy(dimension):
    """生成一个维度为dimension的levy飞行分布"""
    beta = 1.5
    sigma = (np.gamma(1 + beta) * np.sin(np.pi * beta / 2) /
             (np.gamma((1 + beta) / 2) * beta * 2 ** ((beta - 1) / 2))) ** (1 / beta)
    u = np.random.normal(0, 1, dimension) * sigma
    v = np.random.normal(0, 1, dimension)
    step = u / abs(v) ** (1 / beta)
    return 0.01 * step


def repair_precedence_constraints(env, offspring):
    """
    修复后代个体中的优先约束关系。

    参数:
        env (JobShop): 作业车间环境。
        offspring: 后代个体列表。

    返回:
        list: 修复后的后代个体列表。
    """
    precedence_relations = env.precedence_relations_jobs
    for ind in offspring:
        i = 0
        lst = ind[1]
        while i < len(ind[1]):
            if lst[i] in precedence_relations.keys():
                max_index = 0
                for j in precedence_relations[lst[i]]:
                    index = len(lst) - 1 - lst[::-1].index(j)
                    if index > max_index:
                        max_index = index
                if max_index > i:
                    item = lst[i]
                    lst.pop(i)  # 从源索引移除项目
                    lst.insert(max_index, item)
                    continue
            i += 1
    return offspring
